using System;
using System.Collections;
using System.Runtime.InteropServices;

namespace Framework.Interop
{
	[ComVisible(false)]
	public sealed class ItemIDList
	{
		public static ItemIDList Create(IntPtr p)
		{
			return p == IntPtr.Zero ? null : new ItemIDList(p);
		}

		public static unsafe ItemIDList Create(IMalloc m, params byte[][] data)
		{
			if (m == null)
			{
				throw new ArgumentNullException("m");
			}

			int len = ItemID.HeaderSize; // terminator

			foreach (byte[] item in data)
			{
				len += item.Length + ItemID.HeaderSize;
			}

			IntPtr p = m.Alloc(len);

			if (p == IntPtr.Zero)
			{
				throw new OutOfMemoryException("Shell failed to allocate ITEMIDLIST");
			}

			IntPtr workingP = p;

			foreach (byte[] item in data)
			{
				*((ushort*) workingP) = checked ((ushort) (item.Length + ItemID.HeaderSize));
				workingP = (IntPtr) ((int) workingP + ItemID.HeaderSize);
				Marshal.Copy(item, 0, workingP, item.Length);
				workingP = (IntPtr) ((int) workingP + item.Length);
			}

			*((ushort*) workingP) = 0;

			return new ItemIDList(p);
		}

		// gets whether the p points to a null-terminator
		private static unsafe bool IsTerminator(IntPtr p)
		{
			return *((ushort*)p) == 0;
		}

		private readonly IntPtr _pointer;

		private ItemIDList(IntPtr p)
		{
			if (p == IntPtr.Zero)
			{
				throw new ArgumentNullException("p");
			}

			this._pointer = p;
		}

		public byte[][] GetItemData()
		{
			ArrayList data = new ArrayList();

			foreach (ItemID item in GetItems())
			{
				data.Add(item.GetData());
			}

			return (byte[][]) data.ToArray(typeof(byte[]));
		}

		public ItemID[] GetItems()
		{
			ArrayList items = new ArrayList();
			IntPtr itemP = _pointer;

			while (!ItemIDList.IsTerminator(itemP))
			{
				ItemID item = ItemID.Create(itemP);
				items.Add(item);
				itemP = (IntPtr) ((int) itemP + ItemID.HeaderSize + item.DataLength);
			}

			return (ItemID[]) items.ToArray(typeof(ItemID));
		}

		public IntPtr Ptr
		{
			get
			{
				return _pointer;
			}
		}

		public int Size
		{
			get
			{
				int size = ItemID.HeaderSize; // terminator
				
				foreach (ItemID item in GetItems())
				{
					size += item.Size;
				}
				
				return size;
			}
		}
	}
}
